using System;
using System.Collections.Generic;
using NFreeMarker.Ext.Util;
using Environment = NFreeMarker.Core.Environment;

namespace NFreeMarker.Template.Utility
{
	/**
	 * Utility methods for unwrapping {@link TemplateModel}-s.
	 * @author Attila Szegedi
	 * @version $Id: DeepUnwrap.java,v 1.6 2005/06/16 18:13:58 ddekany Exp $
	 */
	public class DeepUnwrap
	{
	    private static readonly Type ObjectClass = typeof (object);

        /**
	     * Unwraps {@link TemplateModel}-s recursively.
	     * The converting of the {@link TemplateModel} object happens with the following rules:
	     * <ol>
	     *   <li>If the object implements {@link AdapterTemplateModel}, then the result
	     *       of {@link AdapterTemplateModel#getAdaptedObject(Class)} for <tt>Object.class</tt> is returned.
	     *   <li>If the object implements {@link WrapperTemplateModel}, then the result
	     *       of {@link WrapperTemplateModel#getWrappedObject()} is returned.
	     *   <li>If the object is identical to the null model of the current object 
	     *       wrapper, null is returned. 
	     *   <li>If the object implements {@link TemplateScalarModel}, then the result
	     *       of {@link TemplateScalarModel#getAsString()} is returned.
	     *   <li>If the object implements {@link TemplateNumberModel}, then the result
	     *       of {@link TemplateNumberModel#getAsNumber()} is returned.
	     *   <li>If the object implements {@link TemplateDateModel}, then the result
	     *       of {@link TemplateDateModel#getAsDate()} is returned.
	     *   <li>If the object implements {@link TemplateBooleanModel}, then the result
	     *       of {@link TemplateBooleanModel#getAsBoolean()} is returned.
	     *   <li>If the object implements {@link TemplateSequenceModel} or
	     *       {@link TemplateCollectionModel}, then a <code>java.util.ArrayList</code> is
	     *       constructed from the subvariables, and each subvariable is unwrapped with
	     *       the rules described here (recursive unwrapping).
	     *   <li>If the object implements {@link TemplateHashModelEx}, then a
	     *       <code>java.util.HashMap</code> is constructed from the subvariables, and each
	     *       subvariable is unwrapped with the rules described here (recursive unwrapping).
	     *   <li>Throw a <code>TemplateModelException</code>, because it doesn't know how to
	     *       unwrap the object.
	     * </ol>
	     */
	    public static object Unwrap(ITemplateModel model)
        {
	        return Unwrap(model, false);
	    }

	    /**
	     * Same as {@link #unwrap(TemplateModel)}, but it doesn't throw exception 
	     * if it doesn't know how to unwrap the model, but rather returns it as-is.
	     * @since 2.3.14
	     */
	    public static object PermissiveUnwrap(ITemplateModel model)
        {
	        return Unwrap(model, true);
	    }
	    
	    /**
	     * @deprecated the name of this method is mistyped. Use 
	     * {@link #permissiveUnwrap(TemplateModel)} instead.
	     */
	    public static object PremissiveUnwrap(ITemplateModel model)
        {
	        return Unwrap(model, true);
	    }
	    
	    private static object Unwrap(ITemplateModel model, bool permissive)
        {
	        Environment env = Environment.GetCurrentEnvironment();
	        ITemplateModel nullModel = null;
	        if (env != null)
            {
	            IObjectWrapper wrapper = env.GetObjectWrapper();
	            if (wrapper != null)
                {
	                nullModel = wrapper.Wrap(null);
	            }
	        }
	        return Unwrap(model, nullModel, permissive);
	    }

	    private static object Unwrap(ITemplateModel model, ITemplateModel nullModel, bool permissive)
        {
	        if (model is IAdapterTemplateModel)
            {
	            return ((IAdapterTemplateModel) model).GetAdaptedObject(ObjectClass);
	        }
	        if (model is IWrapperTemplateModel)
            {
	            return ((IWrapperTemplateModel) model).GetWrappedObject();
	        }
	        if (model == nullModel)
            {
	            return null;
	        }
	        if (model is ITemplateScalarModel)
            {
	            return ((ITemplateScalarModel) model).GetAsString();
	        }
	        if (model is ITemplateNumberModel)
            {
	            return ((ITemplateNumberModel) model).GetAsNumber();
	        }
	        if (model is ITemplateDateModel)
            {
	            return ((ITemplateDateModel) model).GetAsDate();
	        }
	        if (model is ITemplateBooleanModel) {
	            return ((ITemplateBooleanModel) model).GetAsBoolean();
	        }
	        if (model is ITemplateSequenceModel)
            {
	            var seq = (ITemplateSequenceModel) model;
	            var list = new List<object>(seq.Size());
	            for (int i = 0; i < seq.Size(); ++i)
                {
	                list.Add(Unwrap(seq.Get(i), nullModel, permissive));
	            }
	            return list;
	        }
	        if (model is ITemplateCollectionModel)
            {
	            var coll = (ITemplateCollectionModel) model;
	            var list = new List<object>();
	            ITemplateModelIterator it = coll.Iterator();            
	            while (it.HasNext())
                {
	                list.Add(Unwrap(it.Next(), nullModel, permissive));
	            }
	            return list;
	        }
	        if (model is ITemplateHashModelEx)
            {
	            var hash = (ITemplateHashModelEx) model;
	            var map = new Dictionary<string, object>();
	            ITemplateModelIterator keys = hash.Keys().Iterator();
	            while (keys.HasNext())
                {
	                var key = (string) Unwrap(keys.Next(), nullModel, permissive); 
	                map.Add(key, Unwrap(hash.Get(key), nullModel, permissive));
	            }
	            return map;
	        }
	        if (permissive)
            {
	            return model;
	        }
	        throw new TemplateModelException("Cannot deep-unwrap model of type " + model.GetType().FullName);
	    }
	}
}